#ifndef POLYVOX_GENERATOR_H
#define POLYVOX_GENERATOR_H

#include "GeometryGenerator.h"
#include <PolyVoxCore/LargeVolume.h>
#include <PolyVoxCore/Array.h>
#include <boost/thread.hpp>
#include <boost/unordered_map.hpp>
#include <queue>
#include <utility>
#include <OgreVector3.h>
#include <iostream>

namespace PolyVox
{
    template <class Type, uint8_t NoOfMaterialBits, uint8_t NoOfDensityBits>
    class MaterialDensityPair;
    template <class VoxelType>
    class ConstVolumeProxy;
    class Region;
    class PositionMaterialNormal;
    template <class T>
    class SurfaceMesh;
};

namespace Ogre
{
    class SceneManager;
};

namespace Forests
{
    class PagedGeometry;
    class TreeLoader3D;
};

typedef PolyVox::MaterialDensityPair<uint16_t, 12, 4> DFVoxel;

class CompareBlockPriority
{
public:
    CompareBlockPriority(const bool& revparam=false)
    {
        reverse = revparam;
    }
    bool operator() (const std::pair<Ogre::Real,Ogre::Vector3>& lhs, const std::pair<Ogre::Real,Ogre::Vector3>& rhs)
    {
        if (reverse) return (lhs.first<rhs.first);
        else return (lhs.first>rhs.first);
    }
private:
    bool reverse;
};

typedef struct
{
    size_t operator() (Ogre::Vector3 const& v) const
    {
        size_t hash = 0;
        boost::hash_combine(hash,v.x);
        boost::hash_combine(hash,v.y);
        boost::hash_combine(hash,v.z);
        return hash;
    }
} Vector3Hash;

typedef struct
{
    Ogre::Vector3 position;
    PolyVox::SurfaceMesh<PolyVox::PositionMaterialNormal>* mesh;
    PolyVox::Array<3,uint8_t>* ambient;
} MeshRenderInfo;

class PolyVoxGenerator : public GeometryGenerator
{
public:
    PolyVoxGenerator();
    ~PolyVoxGenerator();
    void setMapSize(uint32_t x, uint32_t y, uint32_t z);
    void setTile(uint32_t x, uint32_t y, uint32_t z, TileType tile_type, TileMaterialType tile_material=NONE, uint32_t material_index=0, uint16_t material_type=0);
    void addLiquid(uint32_t x, uint32_t y, uint32_t z, LiquidType liquid_type, uint16_t flow_size);
    void addTree(uint32_t x, uint32_t y, uint32_t z, uint32_t material, bool is_shrub);
    void notifyCameraPosition(Ogre::Vector3 camPos);
    void setCamera(Ogre::Camera *c);
    Ogre::Vector3 getInitialCameraPosition();
    void update(double timeSinceLastFrame);
    void prepareScene();
private:
    void drawPage(MeshRenderInfo &renderInfo);
    void unloadPage(Ogre::Vector3 v);
    void pagePreparationThread();
    void pageSelectionThread();
    void drawVertex(Ogre::ManualObject *mesh, const PolyVox::Vector3DFloat &position, const PolyVox::Vector3DFloat &normal, const Ogre::ColourValue &colour, const Ogre::Real &texture);
    void decodeMaterial(uint16_t index, Ogre::ColourValue *colour, Ogre::Real *texture);
    void dataRequiredHandler(const PolyVox::ConstVolumeProxy<DFVoxel>&, const PolyVox::Region&);
    void dataOverflowHandler(const PolyVox::ConstVolumeProxy<DFVoxel>&, const PolyVox::Region&);
    boost::thread generationThread, pagingThread;
    boost::mutex cameraPositionMutex, pageRenderMutex, pageGenerationMutex, loadedPageMutex;
    PolyVox::LargeVolume<DFVoxel> volume;
    std::queue<MeshRenderInfo> pageRenderQueue;
    std::priority_queue<std::pair<Ogre::Real,Ogre::Vector3>, std::vector<std::pair<Ogre::Real,Ogre::Vector3> >, CompareBlockPriority> pageGenerationQueue;
    boost::unordered_map<Ogre::Vector3,double,Vector3Hash> loadedPages;
    Ogre::Vector3 cameraPosition;
    Ogre::Real x_scale, y_scale, z_scale;
    int page_x_size, page_y_size, page_z_size, map_x_size, map_y_size, map_z_size;
    int page_x_dist, page_y_dist, page_z_dist;
    Ogre::Real highestPoint;
    float decimationFactor;
    bool useFaceNormals, finished, decimateMesh;

    Forests::PagedGeometry *trees;
    Forests::TreeLoader3D *treeloader;
    typedef struct
    {
        Ogre::Vector3 position;
        std::string name;
        bool is_shrub;
    } Plant;

    std::vector<Plant> plants;

    static const uint16_t CONSTRUCTED_MASK = 1<<11;

    enum TextureIndex
    {
        TEXTURE_WATER,
        TEXTURE_MAGMA,
        TEXTURE_ICE,
        TEXTURE_GRASS, //last "default" material
        TEXTURE_ROUGH,
        TEXTURE_CONSTRUCTED,
        TEXTURE_WOOD,
        TEXTURE_SNOW,
        TEXTURE_SOIL
    };

    static const uint16_t NUM_DEFAULT_MATERIALS = 4;
};

#endif
